{#
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
 distributed with this work for additional information
 regarding copyright ownership.  The ASF licenses this file
 to you under the Apache License, Version 2.0 (the
 "License"); you may not use this file except in compliance
 with the License.  You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing,
 software distributed under the License is distributed on an
 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License.
#}

{% extends base_template %}

{% block page_title %}
  {% if search_query %}
    "{{ search_query }}" - DAGs - Airflow
  {% else %}
    DAGs - Airflow
  {% endif %}
{% endblock %}

{% block head_css %}
  {{ super() }}
  <link rel="stylesheet" type="text/css" href="{{ url_for_asset('switch.css') }}">
{% endblock %}

{% block content %}
  <h2>DAGs</h2>

  <div id="main_content">
    <div class="dags-table-wrap">
      <div class="row dags-table-header">
        <div class="col-sm-4">
          <div class="btn-group">
            <a href="{{ url_for('Airflow.index', status='all', search=request.args.get('search', None), tags=request.args.get('tags', None)) }}" class="btn {{'btn-primary' if status_filter == 'all' else 'btn-default'}}" title="Show active and paused DAGS">All <span class="badge">{{ "{:,}".format(status_count_all) }}</span></a>
            <a href="{{ url_for('Airflow.index', status='active', search=request.args.get('search', None), tags=request.args.get('tags', None)) }}" class="btn {{'btn-primary' if status_filter == 'active' else 'btn-default'}}" title="Show only active DAGS">Active <span class="badge">{{ "{:,}".format(status_count_active) }}</span></a>
            <a href="{{ url_for('Airflow.index', status='paused', search=request.args.get('search', None), tags=request.args.get('tags', None)) }}" class="btn {{'btn-primary' if status_filter == 'paused' else 'btn-default'}}" title="Show only paused DAGS">Paused <span class="badge">{{ "{:,}".format(status_count_paused) }}</span></a>
          </div>
        </div>
        <div class="col-sm-4">
          <form id="filter_form" class="form-inline" style="width: 100%; text-align: left;">
            <div class="form-group" style="width: 100%;">
              <select multiple name="tags" id="tags_filter" class="select2-drop-mask" style="width: 60%;">
                {% for tag in tags %}
                  <option value="{{ tag.name }}" {% if tag.selected %}selected{% endif %}>{{ tag.name }}</option>
                {% endfor %}
              </select>
              <input type="submit" value="Filter tags" class="btn btn-default">
              <input type="submit" value="Reset" class="btn btn-default" name="reset_tags">
            </div>
          </form>
        </div>
        <div class="col-sm-4">
          <form id="search_form" class="form-inline">
            <div class="form-group" style="width: 100%;">
              <label for="dag_query" class="sr-only">Search DAGs</label>
              <input type="search" id="dag_query" class="typeahead form-control" data-provide="typeahead" style="width:100%;" value="{{search_query}}" autocomplete="off" placeholder="Search DAGs">
            </div>
          </form>
        </div>
      </div>
      <table class="table table-striped table-bordered table-hover">
        <thead>
          <tr>
            <th width="12">
              <span id="pause_header" class="material-icons text-muted" title="Use this toggle to pause a DAG. The scheduler won't schedule new tasks instances for a paused DAG. Tasks already running at pause time won't be affected.">info</span>
            </th>
            <th>DAG</th>
            <th>Schedule</th>
            <th>Owner</th>
            <th>
              Recent Tasks
              <span class="material-icons text-muted js-tooltip" aria-hidden="true" title="Status of tasks from all active DAG runs or, if not currently active, from most recent run.">info</span>
              <img class="loading-task-stats" width="15" src="{{ url_for('static', filename='loading.gif') }}">
            </th>
            <th>
              Last Run
              <span class="material-icons text-muted js-tooltip" aria-hidden="true" title="Execution Date/Time of Highest Dag Run.">info</span>
            </th>
            <th>
              DAG Runs
              <span class="material-icons text-muted js-tooltip" aria-hidden="true" title="Status of all previous DAG runs.">info</span>
              <img class="loading-dag-stats"  width="15" src="{{ url_for('static', filename='loading.gif') }}">
            </th>
            <th class="text-center" style="width:205px;">Links</th>
            <th class="text-center" style="width:140px;">Actions</th>
          </tr>
        </thead>
        <tbody>
          {% if dags|length == 0 %}
            <tr><td colspan="9">No results</td></tr>
          {% endif %}
          {% for dag in dags %}
            <tr>
              {# Column 1: Turn dag on/off #}
              <td>
                <label class="switch-label js-tooltip" title="Pause/Unpause DAG">
                  <input class="switch-input" id="toggle-{{ dag.dag_id }}" dag_id="{{ dag.dag_id }}" type="checkbox" {{ "checked" if not dag.is_paused else "" }} method="post">
                  <span class="switch" aria-hidden="true"></span>
                </label>
              </td>
              {# Column 2: Name #}
              <td>
                <span>
                  <a href="{{ url_for('Airflow.'+ dag.get_default_view(), dag_id=dag.dag_id) }}"
                     title="{{ dag.description[0:80] + '…' if dag.description and dag.description|length > 80 else dag.description|default('', true) }}">
                    <strong>{{ dag.dag_id }}</strong>
                  </a>
                </span>
                <div style="float: right; max-width: 70%; text-align: right; line-height: 160%;">
                  {% for tag in dag.tags | sort(attribute='name') %}
                    <a class="label label-success"
                       href="?tags={{ tag.name }}"
                       style="margin: 3px;">
                       {{ tag.name }}
                    </a>
                  {% endfor %}
                </div>
              </td>
              {# Column 3: Dag Schedule #}
              <td>
                <a class="label label-default schedule" href="{{ url_for('DagRunModelView.list') }}?_flt_3_dag_id={{ dag.dag_id }}">
                  {{ dag.schedule_interval }}
                </a>
              </td>
              {# Column 4: Dag Owners #}
              <td>
                {{ dag.owners }}
              </td>
              {# Column 5: Recent Tasks #}
              <td style="padding:0; width:323px; height:10px;">
                <svg height="10" width="10" id='task-run-{{ dag.safe_dag_id }}' style="display: block;"></svg>
              </td>
              {# Column 6: Last Run #}
              <td class="text-nowrap latest_dag_run">
                <div height="10" width="10" id='last-run-{{ dag.safe_dag_id }}' style="display: block;">
                  <a></a>
                  <img class="loading-last-run" width="15" src="{{ url_for('static', filename='loading.gif') }}">
                  <span aria-hidden="true" title=" " class="material-icons text-muted js-tooltip" style="display:none">info</span>
                </div>
              </td>
              {# Column 7: Dag Runs #}
              <td style="padding:0; width:120px;">
                <svg height="10" width="10" id='dag-run-{{ dag.safe_dag_id }}' style="display: block;"></svg>
              </td>
              {# Column 8: Links #}
              <td class="text-center">
                {% if dag %}
                  <a href="{{ url_for('Airflow.tree', dag_id=dag.dag_id, num_runs=num_runs) }}" class="js-tooltip" title="Tree View" aria-label="Tree View">
                    <span class="material-icons" aria-hidden="true">nature</span>
                  </a>
                  <a href="{{ url_for('Airflow.graph', dag_id=dag.dag_id) }}" class="js-tooltip" title="Graph View" aria-label="Graph View">
                    <span class="material-icons" aria-hidden="true">account_tree</span>
                  </a>
                  <a href="{{ url_for('Airflow.duration', dag_id=dag.dag_id) }}" class="js-tooltip" title="Task Duration" aria-label="Task Duration">
                    <span class="material-icons" aria-hidden="true">hourglass_bottom</span>
                  </a>
                  <a href="{{ url_for('Airflow.tries', dag_id=dag.dag_id) }}" class="js-tooltip" title="Task Tries" aria-label="Task Tries">
                    <span class="material-icons" aria-hidden="true">repeat</span>
                  </a>
                  <a href="{{ url_for('Airflow.landing_times', dag_id=dag.dag_id) }}" class="js-tooltip" title="Landing Times" aria-label="Landing Times">
                    <span class="material-icons" aria-hidden="true">flight_land</span>
                  </a>
                  <a href="{{ url_for('Airflow.gantt', dag_id=dag.dag_id) }}" class="js-tooltip" title="Gantt View" aria-label="Gantt">
                    <span class="material-icons" aria-hidden="true" data-original-title="Gantt">vertical_distribute</span>
                  </a>
                  <a href="{{ url_for('Airflow.dag_details', dag_id=dag.dag_id) }}" class="js-tooltip" title="DAG Dtails" aria-label="DAG Details">
                    <span class="material-icons" aria-hidden="true">details</span>
                  </a>
                  <a href="{{ url_for('Airflow.code', dag_id=dag.dag_id) }}" class="js-tooltip" title="Code View" aria-label="Code">
                    <span class="material-icons" aria-hidden="true">code</span>
                  </a>
                {% endif %}
              </td>
              {# Column 9: Actions #}
              <td class="text-center">
                <div class="btn-group">
                  {% if dag %}
                    <a href="{{ url_for('Airflow.trigger', dag_id=dag.dag_id) }}" title="Trigger DAG" aria-label="Trigger DAG" class="btn btn-sm btn-default btn-icon-only">
                      <span class="material-icons" aria-hidden="true">play_arrow</span>
                    </a>
                    <a href="{{ url_for('Airflow.refresh', dag_id=dag.dag_id) }}" onclick="postAsForm(this.href); return false" title="Refresh DAG" aria-label="Refresh DAG" class="btn btn-sm btn-default btn-icon-only">
                      <span class="material-icons" aria-hidden="true">refresh</span>
                    </a>
                  {% endif %}
                  {# Use dag_id instead of dag.dag_id, because the DAG might not exist in the webserver's DagBag #}
                  <a href="{{ url_for('Airflow.delete', dag_id=dag.dag_id) }}" onclick="return confirmDeleteDag(this, '{{ dag.dag_id }}')" title="Delete&nbsp;DAG" aria-label="Delete DAG" class="btn btn-sm btn-default btn-icon-only">
                    <span class="material-icons text-danger" aria-hidden="true">delete_outline</span>
                  </a>
                </div>
              </td>
            </tr>
          {% endfor %}
        </tbody>
      </table>
    </div>
    <div class="row">
      <div class="col-sm-6">
        {{paging}}
      </div>
      <div class="col-sm-6 text-right text-muted">
        Showing <strong>{{num_dag_from}}-{{num_dag_to}}</strong> of <strong>{{num_of_all_dags}}</strong> DAGs
      </div>
    </div>
  </div>
  <div id="svg-tooltip" class="tooltip top" style="position: fixed; display: none; opacity: 1">
    <div class="tooltip-arrow"></div>
    <div class="tooltip-inner"></div>
  </div>
{% endblock %}

{% block tail %}
  {{ super() }}
  <script src="{{ url_for_asset('d3.min.js') }}"></script>
  <script>
    const DAGS_INDEX = '{{ url_for('Airflow.index') }}';
    const ENTER_KEY_CODE = 13;
    const STATE_COLOR = {{ state_color|tojson }};

    $('#tags_filter').select2({
      placeholder: 'Filter DAGs by tag',
      allowClear: true
    });

    $('#dag_query').on('keypress', function (e) {
      // check for key press on ENTER (key code 13) to trigger the search
      if (e.which === ENTER_KEY_CODE) {
        var query = new URLSearchParams(window.location.search);
        query.set("search", e.target.value.trim());
        query.delete("page");
        window.location = DAGS_INDEX + "?" + query.toString();
        e.preventDefault();
      }
    });

    $('#page_size').on('change', function() {
      p_size = $(this).val();
      window.location = DAGS_INDEX + "?page_size=" + p_size;
    });

    function confirmTriggerDag(link, dag_id){
      if (confirm("Are you sure you want to run '"+dag_id+"' now?")) {
        postAsForm(link.href, {});
      }
      // Never follow the link
      return false;
    }

    function confirmDeleteDag(link, dag_id){
      if (confirm("Are you sure you want to delete '"+dag_id+"' now?\n\
        This option will delete ALL metadata, DAG runs, etc.\n\
        EXCEPT Log.\n\
        This cannot be undone.")) {
        postAsForm(link.href, {});
      }
      return false;
    }

    var encoded_dag_ids = new URLSearchParams();

    $.each($('[id^=toggle]'), function(i, v) {
      var $input = $(v);
      var dag_id = $input.attr('dag_id');
      encoded_dag_ids.append('dag_ids', dag_id);

      $input.change(function() {
        var $buttons = $input.parent('.toggle').find('.btn');
        $buttons.removeClass('btn-danger');
        if ($input.prop('checked')) {
          is_paused = 'true'
        } else {
          is_paused = 'false'
        }
        var url = 'paused?is_paused=' + is_paused + '&dag_id=' + encodeURIComponent(dag_id);
        $.post(url).fail(function() {
          if (is_paused === 'true') {
            $input.data('bs.toggle').off(true);
          } else {
            $input.data('bs.toggle').on(true);
          }
          $buttons.addClass('btn-danger');
        });
      });
    });

    $('.typeahead').typeahead({
      source: function (query, callback) {
        return $.ajax('{{ url_for('DagModelView.autocomplete') }}',
          {
            data: {
              'query': encodeURIComponent(query),
              'status': '{{ status_filter }}'
            },
            success: callback
          });
      },
      autoSelect: false,
      afterSelect: function(value) {
        var search_query = value.trim();
        if (search_query) {
          var query = new URLSearchParams(window.location.search);
          query.set('search', search_query);
          window.location = DAGS_INDEX + "?" + query;
        }
      }
    });

    $('#main_content').show(250);
    diameter = 25;
    circle_margin = 4;
    stroke_width = 2;
    stroke_width_hover = 6;

    function blockedHandler(error, json) {
      $.each(json, function() {
        $('.label.schedule.' + this.dag_id)
        .attr('title', this.active_dag_run + '/' + this.max_active_runs + ' active dag runs')
        .tooltip();
        if(this.active_dag_run >= this.max_active_runs) {
          $('.label.schedule.' + this.dag_id)
          .css('background-color', 'red');
        }
      });
    }

    function lastDagRunsHandler(error, json) {
      for(var safe_dag_id in json) {
        dag_id = json[safe_dag_id].dag_id;
        last_run = json[safe_dag_id].last_run;
        g = d3.select('div#last-run-' + safe_dag_id)
        g.selectAll('a')
          .attr('href', '{{ url_for('Airflow.graph') }}?dag_id=' + encodeURIComponent(dag_id) + '&execution_date=' + encodeURIComponent(last_run))
          .insert(isoDateToTimeEl.bind(null, last_run, {title: false}));
        g.selectAll('span')
          // We don't translate the timezone in the tooltip, that stays in UTC.
          .attr('data-original-title', 'Start Date: ' + last_run)
          .style('display', null);
        g.selectAll('.loading-last-run').remove();
      }
      d3.selectAll('.loading-last-run').remove();
    }

    function drawDagStatsForDag(dag_id, stats) {
      g = d3.select('svg#dag-run-' + dag_id.replace(/\./g, '__dot__'))
        .attr('height', diameter + (stroke_width_hover * 2))
        .attr('width', '110px')
        .selectAll("g")
        .data(states)
        .enter()
        .append('g')
        .attr('transform', function(d, i) {
          x = (i * (diameter + circle_margin)) + (diameter/2 + circle_margin);
          y = (diameter/2) + stroke_width_hover;
          return 'translate(' + x + ',' + y + ')';
        });

      g.append('circle')
        .attr('id', function(d) {return 'run-' + dag_id.replace(/\./g, '_') + d.state || 'none'})
        .attr('class', 'has-svg-tooltip')
        .attr('stroke-width', function(d) {
          if (d.count > 0)
            return stroke_width;
          else {
            return 1;
          }
        })
        .attr('stroke', function(d) {
          if (d.count > 0)
            return STATE_COLOR[d.state];
          else {
            return 'gainsboro';
          }
        })
        .attr('fill', '#fff')
        .attr('r', diameter/2)
        .attr('title', function(d) {return d.state})
        .attr('style', function(d) {
          if (d.count > 0)
              return"cursor:pointer;"
        })
        .on('click', function(d, i) {
          if (d.count > 0)
            window.location = '{{ url_for('DagRunModelView.list') }}?_flt_3_dag_id=' + dag_id + '&_flt_3_state=' + d.state;
        })
        .on('mouseover', function(d, i) {
          if (d.count > 0) {
            d3.select(this).transition().duration(400)
              .attr('fill', '#e2e2e2')
              .style('stroke-width', stroke_width_hover);
          }
        })
        .on('mouseout', function(d, i) {
          if (d.count > 0) {
            d3.select(this).transition().duration(400)
              .attr('fill', '#fff')
              .style('stroke-width', stroke_width);
          }
        })
        .style('opacity', 0)
        .transition().duration(300).delay(function(d, i){return i*50;})
        .style('opacity', 1);
      d3.select('.loading-dag-stats').remove();

      g.append('text')
        .attr('fill', '#51504f')
        .attr('text-anchor', 'middle')
        .attr('vertical-align', 'middle')
        .attr('font-size', 8)
        .attr('y', 3)
        .text(function(d){ return d.count > 0 ? d.count : ''; });
    }

    function dagStatsHandler(error, json) {
      for(var dag_id in json) {
        states = json[dag_id];
        drawDagStatsForDag(dag_id, states);
      }
    }

    function drawTaskStatsForDag(dag_id, states) {
      g = d3.select('svg#task-run-' + dag_id.replace(/\./g, '__dot__'))
        .attr('height', diameter + (stroke_width_hover * 2))
        .attr('width', (states.length * (diameter + circle_margin)) + circle_margin)
        .selectAll("g")
        .data(states)
        .enter()
        .append('g')
        .attr('transform', function(d, i) {
          x = (i * (diameter + circle_margin)) + (diameter/2 + circle_margin);
          y = (diameter/2) + stroke_width_hover;
          return 'translate(' + x + ',' + y + ')';
        });

      g.append('circle')
        .attr('id', function(d) {return 'task-' + dag_id.replace(/\./g, '_') + d.state || 'none'})
        .attr('class', 'has-svg-tooltip')
        .attr('stroke-width', function(d) {
          if (d.count > 0)
            return stroke_width;
          else {
            return 1;
          }
        })
        .attr('stroke', function(d) {
          if (d.count > 0)
            return STATE_COLOR[d.state];
          else {
            return 'gainsboro';
          }
        })
        .attr('fill', '#fff')
        .attr('r', diameter/2)
        .attr('title', function(d) {return d.state || 'none'})
        .attr('style', function(d) {
          if (d.count > 0)
              return"cursor:pointer;"
        })
        .on('click', function(d, i) {
          if (d.count > 0)
            window.location = '{{ url_for('TaskInstanceModelView.list') }}?_flt_3_dag_id=' + dag_id + '&_flt_3_state=' + d.state;
        })
        .on('mouseover', function(d, i) {
          if (d.count > 0) {
            d3.select(this).transition().duration(400)
              .attr('fill', '#e2e2e2')
              .style('stroke-width', stroke_width_hover);
          }
        })
        .on('mouseout', function(d, i) {
          if (d.count > 0) {
            d3.select(this).transition().duration(400)
              .attr('fill', '#fff')
              .style('stroke-width', stroke_width);
          }
        })
        .style('opacity', 0)
        .transition().duration(300).delay(function(d, i){return i*50;})
        .style('opacity', 1);
      d3.select('.loading-task-stats').remove();

      g.append('text')
        .attr('fill', '#51504f')
        .attr('text-anchor', 'middle')
        .attr('vertical-align', 'middle')
        .attr('font-size', 8)
        .attr('y', 3)
        .text(function(d){ return d.count > 0 ? d.count : ''; });
    }

    function taskStatsHandler(error, json) {
      for(var dag_id in json) {
        states = json[dag_id];
        drawTaskStatsForDag(dag_id, states);
      }
    }

    if (encoded_dag_ids.has('dag_ids')) {
      // dags on page fetch stats
      d3.json('{{ url_for('Airflow.blocked') }}')
        .header('X-CSRFToken', '{{ csrf_token() }}')
        .post(encoded_dag_ids, blockedHandler);
      d3.json('{{ url_for('Airflow.last_dagruns') }}')
        .header('X-CSRFToken', '{{ csrf_token() }}')
        .post(encoded_dag_ids, lastDagRunsHandler);
      d3.json('{{ url_for('Airflow.dag_stats') }}')
        .header('X-CSRFToken', '{{ csrf_token() }}')
        .post(encoded_dag_ids, dagStatsHandler);
      d3.json('{{ url_for('Airflow.task_stats') }}')
        .header('X-CSRFToken', '{{ csrf_token() }}')
        .post(encoded_dag_ids, taskStatsHandler);
    }
    else {
      // no dags, hide the loading gifs
      $('.loading-task-stats').remove();
      $('.loading-dag-stats').remove();
    }

    function showSvgTooltip(text, circ) {
      var tip = $('#svg-tooltip');
      tip.children('.tooltip-inner').text(text);
      var centeringOffset = tip.width() / 2;
      tip.css({
        'display': 'block',
        'left': circ.left + 12.5 - centeringOffset + 'px',// 12.5 == half of circle width
        'top': circ.top - 25 + 'px'// 25 == position above circle
      });
    }

    function hideSvgTooltip() {
      $('#svg-tooltip').css('display', 'none');
    }

    $(window).on('load', function() {
      $('body').on('mouseover', '.has-svg-tooltip', function(e) {
        var elem = e.target;
        var text = elem.getAttribute('title');
        var circ = elem.getBoundingClientRect();
        showSvgTooltip(text, circ);
      });

      $('body').on('mouseout', '.has-svg-tooltip', function(e) {
        hideSvgTooltip();
      });
    });
  </script>
{% endblock %}
